const obj = {
    get foo() {
        console.log(this);
        return this.bar;
    }
};
// generate a new proxy object
const proxy = new Proxy(obj, {
    get(target, key, receiver) {
        if (key === 'bar') {
            return 'bar'
        }
        return Reflect.get(target, key, receiver);
    }
})
console.log(proxy.foo);
// Output:

// define a function that will be called when judge if the prameter is object
const isObject = (val) => val !== null && typeof val === 'object';

// define a function that will be called when judge if the prameter is object 
// if the prameter is object, return the new proxy object
const convert = (target) => isObject(target) ? reactive(target) : target;

// export a function that will use to generate a new proxy object
export function reactive(target) {
    if (!isObject(target)) {
        return target;
    }
    const handler = {
        get: (target, key, receiver) => {
            const result = Reflect.get(target, key, receiver);
            track(target, key);
            return convert(result);
        },
        set: (target, key, value, receiver) => {
            const oldValue = Reflect.get(target, key, receiver);
            let result = Reflect.set(target, key, value, receiver);
            if (oldValue !== value) {
                result = Reflect.set(target, key, value, receiver);
                // trigger the watcher
                trigger(target, key);
                return result;
            }
            return result;

        },
        deleteProperty: (target, key) => {
            const hadKey = hasOwn(target, key);
            const result = Reflect.deleteProperty(target, key);
            if (hadKey && result) {
                trigger(target, key);
            }
            return result;
        }
    };
    return new Proxy(target, handler);
}

let activeEffect = null;
export function effect(callback) {
    activeEffect = callback;
    activeEffect();
    activeEffect = null;
}


let targetMap = new WeakMap();

export function track(target, key) {
    if (!activeEffect) return;
    let depsMap = targetMap.get(target);
    if (!depsMap) { // if the target is not in the targetMap, add it
        targetMap.set(target, (depsMap = new Map()));
    }
    let deps = depsMap.get(key);
    if (!deps) { // if the key is not in the depsMap, add it
        depsMap.set(key, (deps = new Set()));
    }
    deps.add(activeEffect);
}

// trigger the watcher
export function trigger(target, key) {
    const depsMap = targetMap.get(target);
    if (!depsMap) return;
    const deps = depsMap.get(key);
    if (!deps) return;
    deps.forEach(effect => effect());
}


export function ref(raw) {
    if (isObject(raw) && raw.__v_isRef) return
    let value = convert(raw);
    const r = {
        __v_isRef: true,
        get value() {
            track(r, 'value');
            return value;
        },
        set value(newVal) { // setter
            if (newVal === value) return;
            raw = newVal;
            value = convert(newVal);
            trigger(r, 'value');
        }
    }
    return r
}

export function toRefs(proxy) {
    const ret = proxy instanceof Array ? new Array(proxy.length) : {};
    for (let key in proxy) {
        ret[key] = toProxyRef(proxy, key);
    }
    return ret;
}

function toProxyRef(proxy, key) {
    const r = {
        __v_isRef: true,
        get value() {
            return proxy[key];
        },
        set value(newVal) {
            proxy[key] = newVal;
        }
    }
    return r
}

// define a function that will be called when judge if the prameter is object
export function computed(getter) {
    const result = ref();
    effect(() => {
        result.value = getter();
    });
    return result;
}